package com.wei.czz.framework.security.manager;

import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.admin.user.RiskCalculateTypeEnum;
import com.wei.czz.common.enums.admin.user.UserRiskStatusEnum;
import com.wei.czz.common.enums.admin.user.UserRiskTypeEnum;
import com.wei.czz.common.enums.common.dict.DictEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.po.CountOptPo;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.po.security.UserOptPo;
import com.wei.czz.common.utils.GuavaUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.common.utils.TimeUtils;
import com.wei.czz.framework.admin.entity.UserEntity;
import com.wei.czz.framework.admin.entity.UserRiskCalculateFlowEntity;
import com.wei.czz.framework.admin.entity.UserRiskFlowEntity;
import com.wei.czz.framework.admin.service.UserRiskCalculateFlowService;
import com.wei.czz.framework.admin.service.UserRiskFlowService;
import com.wei.czz.framework.admin.service.UserService;
import com.wei.czz.framework.common.entity.DictEntity;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.framework.common.service.AsyncService;
import com.wei.czz.framework.common.wrapper.DictWrapper;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-06-12 11:50:16
 * className: RequestLimitService 请求限制计算服务类
 * version: 1.0
 * description:
 */
@Service
@AllArgsConstructor
public class RequestLimitManager {

    private static final Logger log = LoggerFactory.getLogger(RequestLimitManager.class);

    private final UserService userService;

    private final UserRiskCalculateFlowService userRiskCalculateFlowService;

    private final UserRiskFlowService userRiskFlowService;

    private final RedisStringHandler redisStringHandler;

    private final RedissonClient redissonClient;

    private final AsyncService asyncService;

    private final DictWrapper dictWrapper;

    /**
     * 获取缓存键
     * @param userId    用户主键
     * @param requestIp 请求ip
     * @return 缓存键
     */
    public String getCacheKey(Long userId, String requestIp) {
        return RedisConstant.STRING + Constant.REQUEST_LIMIT + RedisConstant.SPLIT
                + (Objects.isNull(userId) ? requestIp : userId);
    }

    /**
     * 获取用户风控计算缓存键
     * @param userId 用户主键
     * @param type   风控计算类型
     * @return 缓存键
     */
    public String getUserRiskKey(Long userId, Integer type) {
        return RedisConstant.STRING + Constant.USER_RISK + RedisConstant.SPLIT + userId + RedisConstant.SPLIT + type;
    }

    /**
     * 请求限制计算
     * @param userId    用户主键
     * @param requestIp 请求ip
     */
    public void requestLimit(Long userId, String requestIp) {
        // 获取缓存键
        String cacheKey = this.getCacheKey(userId, requestIp);
        String lockKey = RedisConstant.LOCK + cacheKey;

        // 获取当前系统时间戳
        long now = System.currentTimeMillis();

        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(lockKey);
        // 加锁30秒
        lock.lock();
        try {
            /*
                获取请求操作统计
             */
            CountOptPo countOpt = redisStringHandler.get(cacheKey);
            // 首次请求
            if (countOpt == null) {
                countOpt = new CountOptPo(1, 0, 0, now, now);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }
            // 判断请求是否被锁定
            if (Objects.nonNull(countOpt.getUnlockTime())) {
                // 判断锁定时间是否过期
                if (now < countOpt.getUnlockTime()) {
                    log.info("请求（userId={} requestIp={}）已被锁定，此次计算丢弃", userId, requestIp);
                } else {
                    // 刷新数据
                    this.refreshCountOpt(countOpt, now);
                    countOpt.setRequestCount(1);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
                }
                return;
            }
            // 刷新统计数据（超过一分钟）
            if (now - countOpt.getFirstOptTime() >= Constant.MINUTE_NUM) {
                // 刷新数据
                this.refreshCountOpt(countOpt, now);
                countOpt.setRequestCount(1);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }

            // 客户连续请求操作
            if (now - countOpt.getLastOptTime() <= 2000) {
                // 连续请求操作次数自增
                int lastCount = countOpt.getLastCount() + 1;

                /*
                    获取字典值
                 */
                int maxLastCount = this.getDictValue(DictEnum.MAX_LAST_COUNT, 30);

                /*
                    判断用户连续请求次数是否超过系统配置的最大连续请求次数
                    （用户连续请求次数超过系统配置的最大连续请求次数，系统将锁定该用户的请求）
                 */
                if (lastCount >= maxLastCount) {
                    log.warn("来自（userId={} requestIp={}）的连续请求次数已超过配置的最大连续请求次数（{}），锁定来自该来源的请求",
                            userId, requestIp, maxLastCount);
                    // 锁定一分钟
                    countOpt.setUnlockTime(now + Constant.MINUTE_NUM);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10),
                            TimeUnit.SECONDS);

                    if (Objects.nonNull(userId)) {
                        // 异步进行用户锁定风控计算
                        asyncService.execute(() -> {
                            this.userLockFail(userId);
                        });
                    }

                    return;
                }

                // 最新连续操作次数
                countOpt.setLastCount(lastCount);
            } else {
                // 连续操作次数清零
                countOpt.setLastCount(0);
            }
            // 请求操作次数自增
            int count = countOpt.getRequestCount() + 1;

            // 最新操作次数
            countOpt.setRequestCount(count);
            // 最近一次操作时间
            countOpt.setLastOptTime(now);

            /*
                获取字典值
             */
            int maxCountOpt = this.getDictValue(DictEnum.MAX_COUNT_OPT, 150);

            /*
                判断用户请求次数是否超过系统配置的最大请求次数
                （用户请求次数超过系统配置的最大请求次数，系统将锁定该用户的请求）
             */
            if (count >= maxCountOpt) {
                log.warn("来自（userId={} requestIp={}）的最大请求次数已超过系统配置的最大请求次数（{}），系统将锁定来自该来源的请求",
                        userId, requestIp, maxCountOpt);

                // 锁定一分钟
                countOpt.setUnlockTime(now + Constant.MINUTE_NUM);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                if (Objects.nonNull(userId)) {
                    // 异步进行用户锁定风控计算
                    asyncService.execute(() -> {
                        this.userLockFail(userId);
                    });
                }

                return;
            }

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 请求错误计算
     * @param requestIp 请求ip
     * @param code      状态码
     */
    public void requestFail(String requestIp, Integer code) {
        log.info("请求失败记录。requestIp={} code={}", requestIp, code);
        List<ResultEnum> resultEnumList = ResultEnum.getRiskResultList();
        boolean flag = true;
        for (ResultEnum resultEnum : resultEnumList) {
            if (resultEnum.getCode().equals(code)) {
                flag = false;
                break;
            }
        }
        if (flag) {
            log.info("状态不符合条件，结束风控操作");
            return;
        }

        // 获取当前系统时间戳
        long now = System.currentTimeMillis();

        // 获取缓存键
        String cacheKey = this.getCacheKey(null, requestIp);
        String lockKey = RedisConstant.LOCK + cacheKey;

        RLock lock = redissonClient.getLock(lockKey);
        // 加锁
        lock.lock();

        try {
            /*
                获取请求操作统计
             */
            CountOptPo countOpt = redisStringHandler.get(cacheKey);
            // 首次请求
            if (countOpt == null) {
                countOpt = new CountOptPo(0, 0, 1, now, now);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }
            // 判断请求是否被锁定
            if (Objects.nonNull(countOpt.getUnlockTime())) {
                // 判断锁定时间是否过期
                if (now < countOpt.getUnlockTime()) {
                    log.info("请求（requestIp={}）已被锁定，此次计算丢弃", requestIp);
                } else {
                    // 刷新数据
                    this.refreshCountOpt(countOpt, now);
                    countOpt.setFailCount(1);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
                }
                return;
            }
            // 刷新统计数据（超过一分钟）
            if (now - countOpt.getFirstOptTime() >= Constant.MINUTE_NUM) {
                // 刷新数据
                this.refreshCountOpt(countOpt, now);
                countOpt.setFailCount(1);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }

            // 请求失败次数自增
            int failCount = countOpt.getFailCount() + 1;

            /*
                获取字典值
             */
            int maxRequestFailNum = this.getDictValue(DictEnum.IP_MAX_REQUEST_FAIL_NUM, 5);

            // 最新操作次数
            countOpt.setFailCount(failCount);

            // 如果请求失败次数超过限制条件，则将禁用
            if (failCount >= maxRequestFailNum) {
                log.warn("来自（requestIp={}）的请求失败次数已超过配置的最大请求失败次数（{}），锁定来自该来源的请求", requestIp,
                        maxRequestFailNum);

                // 锁定五分钟
                long unlockTime = now + Constant.FIVE_MINUTE_NUM;
                countOpt.setUnlockTime(unlockTime);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    /**
     * 用户登录失败风控计算
     * @param userId 用户主键
     */
    public void userLoginFail(Long userId) {
        long now = System.currentTimeMillis();

        String cacheKey = this.getUserRiskKey(userId, RiskCalculateTypeEnum.LOGIN_FAIL.getValue());
        String lockKey = RedisConstant.LOCK + cacheKey;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();

        try {
            log.info("用户【{}】{}风控计算", userId, RiskCalculateTypeEnum.LOGIN_FAIL.getName());

            /*
                保存用户风控计算流水
             */
            this.saveUserRiskCalculateFlow(userId, RiskCalculateTypeEnum.REQUEST_FAIL.getValue());

            UserOptPo userOptPo = redisStringHandler.get(cacheKey);
            // 首次计算
            if (Objects.isNull(userOptPo)) {
                userOptPo = new UserOptPo(0, now);
                userOptPo.setCount(1);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }
            // 判断用户是否被锁定
            if (Objects.nonNull(userOptPo.getUnlockTime())) {
                // 判断锁定时间是否过期
                if (now < userOptPo.getUnlockTime()) {
                    log.info("用户已经被禁用，此次登录失败风控计算丢弃");
                } else {
                    // 重置数据
                    userOptPo.setCount(1)
                            .setFirstOptTime(now);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
                }
                return;
            }
            // 刷新统计数据（超过一分钟）
            if (now - userOptPo.getFirstOptTime() >= Constant.MINUTE_NUM) {
                // 重置数据
                userOptPo.setCount(1)
                        .setFirstOptTime(now);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }

            // 请求失败次数自增
            int count = userOptPo.getCount() + 1;

            // 最新登录失败次数
            userOptPo.setCount(count);

            int maxLoginFailCount = 10;
            if (count >= maxLoginFailCount) {
                log.warn("来自（userId={}）的{}次数已超过配置的最大{}次数（{}），锁定来自该用户的请求", userId,
                        RiskCalculateTypeEnum.LOGIN_FAIL.getName(), RiskCalculateTypeEnum.LOGIN_FAIL.getName(), maxLoginFailCount);

                // 锁定五分钟
                long unlockTime = now + Constant.FIVE_MINUTE_NUM;
                userOptPo.setUnlockTime(unlockTime);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                if (Objects.nonNull(userId)) {
                    // 异步进行用户锁定风控计算
                    asyncService.execute(() -> {
                        this.userLockFail(userId);
                    });
                }

                asyncService.execute(() -> {
                    // 锁定用户请求
                    this.lockRequest(userId, null, Constant.MINUTE_NUM);
                });

                return;
            }

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    /**
     * 重置用户登录失败次数操作
     * @param userId 用户主键
     */
    public void resetSignFail(Long userId) {
        // 获取缓存键
        String cacheKey = this.getUserRiskKey(userId, RiskCalculateTypeEnum.LOGIN_FAIL.getValue());
        String lockKey = RedisConstant.LOCK + cacheKey;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();

        try {
            log.info("重置用户【{}】{}次数", userId, RiskCalculateTypeEnum.LOGIN_FAIL.getName());

            UserOptPo userOptPo = redisStringHandler.get(cacheKey);
            // 首次计算
            if (Objects.nonNull(userOptPo)) {
                // 重置数据
                userOptPo.setCount(0)
                        .setFirstOptTime(userOptPo.getFirstOptTime() - Constant.MINUTE_NUM);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

            }

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    /**
     * 用户请求失败风控计算
     * @param userId 用户主键
     * @param code   状态码
     */
    public void userRequestFail(Long userId, Integer code) {
        List<ResultEnum> resultEnumList = ResultEnum.getRiskResultList();
        boolean flag = true;
        for (ResultEnum resultEnum : resultEnumList) {
            if (resultEnum.getCode().equals(code)) {
                flag = false;
                break;
            }
        }
        if (flag) {
            log.info("状态不符合条件，不做用户请求失败风控计算操作。code={}", code);
            return;
        }
        long now = System.currentTimeMillis();

        String cacheKey = this.getUserRiskKey(userId, RiskCalculateTypeEnum.REQUEST_FAIL.getValue());
        String lockKey = RedisConstant.LOCK + cacheKey;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();

        try {
            log.info("用户【{}】{}风控计算", userId, RiskCalculateTypeEnum.REQUEST_FAIL.getName());

            /*
                保存用户风控计算流水
             */
            this.saveUserRiskCalculateFlow(userId, RiskCalculateTypeEnum.REQUEST_FAIL.getValue());

            UserOptPo userOptPo = redisStringHandler.get(cacheKey);
            // 首次计算
            if (Objects.isNull(userOptPo)) {
                userOptPo = new UserOptPo(0, now);
                userOptPo.setCount(1);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }
            // 判断用户是否被锁定
            if (Objects.nonNull(userOptPo.getUnlockTime())) {
                // 判断锁定时间是否过期
                if (now < userOptPo.getUnlockTime()) {
                    log.info("用户已经被禁用，此次请求失败风控计算丢弃");
                } else {
                    // 重置数据
                    userOptPo.setCount(1)
                            .setFirstOptTime(now);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
                }
                return;
            }
            // 刷新统计数据（超过一分钟）
            if (now - userOptPo.getFirstOptTime() >= Constant.MINUTE_NUM) {
                // 重置数据
                userOptPo.setCount(1)
                        .setFirstOptTime(now);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                return;
            }

            // 请求失败次数自增
            int count = userOptPo.getCount() + 1;

            // 最新登录失败次数
            userOptPo.setCount(count);

            /*
                获取字典值
             */
            int maxRequestFailCount = this.getDictValue(DictEnum.USER_MAX_REQUEST_FAIL_NUM, 200);

            if (count >= maxRequestFailCount) {
                log.warn("来自（userId={}）的{}次数已超过配置的最大{}次数（{}），锁定来自该用户的请求", userId,
                        RiskCalculateTypeEnum.REQUEST_FAIL.getName(), RiskCalculateTypeEnum.REQUEST_FAIL.getName(),
                        maxRequestFailCount);

                // 锁定五分钟
                long unlockTime = now + Constant.FIVE_MINUTE_NUM;
                userOptPo.setUnlockTime(unlockTime);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

                if (Objects.nonNull(userId)) {
                    // 异步进行用户锁定风控计算
                    asyncService.execute(() -> {
                        this.userLockFail(userId);
                    });
                }

                asyncService.execute(() -> {
                    // 锁定用户请求
                    this.lockRequest(userId, null, Constant.FIVE_MINUTE_NUM);
                });

                return;
            }

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, userOptPo, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }

    }

    /**
     * 用户锁定风控计算
     * @param userId 用户主键
     */
    public void userLockFail(Long userId) {

        long now = System.currentTimeMillis();

        // 获取缓存键
        String cacheKey = this.getUserRiskKey(userId, RiskCalculateTypeEnum.LOCK_REQUEST.getValue());
        String lockKey = RedisConstant.LOCK + cacheKey;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();

        try {
            log.info("用户【{}】{}风控计算", userId, RiskCalculateTypeEnum.LOCK_REQUEST.getName());

            /*
                保存用户风控计算流水
             */
            this.saveUserRiskCalculateFlow(userId, RiskCalculateTypeEnum.LOCK_REQUEST.getValue());

            // 缓存两天
            long cacheTime = RandomNumUtils.randomNum(60 * 60 * 24 * 2, 10);

            UserOptPo userOptPo = redisStringHandler.get(cacheKey);
            // 首次计算
            if (Objects.isNull(userOptPo)) {
                // 获取当天的开始时间
                Date startDate = TimeUtils.startToDay();

                userOptPo = new UserOptPo(0, startDate.getTime());
                userOptPo.setCount(1);

                /*
                    缓存请求操作统计
                 */
                redisStringHandler.set(cacheKey, userOptPo, cacheTime, TimeUnit.SECONDS);

                return;
            }
            // 判断用户是否被锁定
            if (Objects.nonNull(userOptPo.getUnlockTime())) {
                // 判断锁定时间是否过期
                if (now < userOptPo.getUnlockTime()) {
                    log.info("{}风控计算，用户已经被禁用，此次计算丢弃", RiskCalculateTypeEnum.LOCK_REQUEST.getName());
                } else {
                    // 重置数据
                    userOptPo.setCount(1)
                            .setFirstOptTime(now);

                    // 将最新统计存入Redis中
                    redisStringHandler.set(cacheKey, userOptPo, cacheTime, TimeUnit.SECONDS);
                }
                return;
            }
            // 刷新统计数据（超过一天）
            if (now - userOptPo.getFirstOptTime() >= Constant.ONE_DAY_NUM) {
                // 获取当天的开始时间
                Date startDate = TimeUtils.startToDay();

                // 重置数据
                userOptPo.setCount(1)
                        .setFirstOptTime(startDate.getTime());

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, cacheTime, TimeUnit.SECONDS);

                return;
            }

            // 请求失败次数自增
            int count = userOptPo.getCount() + 1;

            // 最新锁定次数
            userOptPo.setCount(count);

            /*
                获取字典值
             */
            int maxLockCount = this.getDictValue(DictEnum.MAX_LOCK_NUM, 10);

            if (count >= maxLockCount) {
                log.warn("来自（userId={}）的{}次数已超过配置的最大{}次数（{}），锁定来自该用户的请求", userId,
                        RiskCalculateTypeEnum.LOCK_REQUEST.getName(), RiskCalculateTypeEnum.LOCK_REQUEST.getName(),
                        maxLockCount);

                Date date = new Date(now);
                /*
                    用户风控
                 */
                UserEntity updateUser = new UserEntity();
                updateUser.setUserId(userId)
                        .setStatus(CommonEnum.ONE.getValue())
                        .setUpdateTime(date)
                        .setUpdateUser("系统判定")
                        .setUpdateUserId(CommonEnum.ZERO.getLongValue());
                userService.saveOrEdit(updateUser);

                /*
                    保存用户风控记录
                 */
                UserRiskFlowEntity userRiskFlow = new UserRiskFlowEntity();
                userRiskFlow.setUserId(userId)
                        .setRiskType(UserRiskTypeEnum.SYSTEM_RISK_TYPE.getValue())
                        .setRiskReason("您被系统锁定操作次数超过限制次数")
                        .setStatus(UserRiskStatusEnum.NONE_STATUS.getValue())
                        .setDeleteStatus(CommonEnum.ZERO.getValue())
                        .setUpdateTime(date)
                        .setUpdateUser(StringUtils.EMPTY)
                        .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                        .setCreateTime(date)
                        .setCreateUser(StringUtils.EMPTY)
                        .setCreateUserId(CommonEnum.ZERO.getLongValue());
                // 操作数据库用户风险记录表，添加一条用户风险记录
                userRiskFlowService.saveOrEdit(userRiskFlow);

                // 锁定一天
                long unlockTime = now + Constant.ONE_DAY_NUM;
                userOptPo.setUnlockTime(unlockTime);

                // 将最新统计存入Redis中
                redisStringHandler.set(cacheKey, userOptPo, cacheTime, TimeUnit.SECONDS);

                asyncService.execute(() -> {
                    // 锁定用户请求
                    this.lockRequest(userId, null, Constant.ONE_DAY_NUM);
                });

                return;
            }

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, userOptPo, cacheTime, TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    /**
     * 锁定请求
     * @param userId    用户主键
     * @param requestIp 请求ip
     * @param lockTime  锁定时间
     */
    public void lockRequest(Long userId, String requestIp, Integer lockTime) {
        // 获取缓存键
        String cacheKey = this.getCacheKey(userId, requestIp);
        String lockKey = RedisConstant.LOCK + cacheKey;

        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(lockKey);
        // 加锁30秒
        lock.lock();
        try {
            long now = System.currentTimeMillis();

            CountOptPo countOpt = new CountOptPo(0, 0, 0, now, now);
            // 锁定
            countOpt.setUnlockTime(now + lockTime);

            // 将最新统计存入Redis中
            redisStringHandler.set(
                    cacheKey,
                    countOpt,
                    RandomNumUtils.randomNum(1, 10) + lockTime,
                    TimeUnit.MILLISECONDS
            );

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 解除锁定请求
     * @param userId    用户主键
     * @param requestIp 请求ip
     */
    public void unlockRequest(Long userId, String requestIp) {
        // 获取缓存键
        String cacheKey = this.getCacheKey(userId, requestIp);
        String lockKey = RedisConstant.LOCK + cacheKey;

        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(lockKey);
        // 加锁30秒
        lock.lock();
        try {
            long now = System.currentTimeMillis();

            CountOptPo countOpt = new CountOptPo(0, 0, 0, now, now);

            // 将最新统计存入Redis中
            redisStringHandler.set(cacheKey, countOpt, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 客户请求统计数据刷新
     * @param countOpt 客户请求统计对象
     * @param now      操作时间（时间戳）
     */
    private void refreshCountOpt(CountOptPo countOpt, Long now) {
        // 操作总数
        countOpt.setRequestCount(0);
        // 连续操作次数
        countOpt.setLastCount(0);
        // 错误次数
        countOpt.setFailCount(0);
        // 首次操作时间
        countOpt.setFirstOptTime(now);
        // 最后一次操作时间
        countOpt.setLastOptTime(now);
        // 解除锁定时间
        countOpt.setUnlockTime(null);
    }

    /**
     * 保存用户风控计算流水
     * @param userId 用户主键
     * @param type   类型
     */
    private void saveUserRiskCalculateFlow(Long userId, Integer type) {
        // 获取操作用户
        UserPo userPo = new UserPo();
        userPo.setUserId(CommonEnum.ZERO.getLongValue())
                .setUsername(StringUtils.EMPTY);
        Date date = new Date();
        try {
            UserPo _userPo = SecurityUtils.getUser();
            userPo.setUserId(_userPo.getUserId())
                    .setUsername(_userPo.getUsername());
        } catch (Exception e) {
            log.info("保存用户风控计算流水，没有登录用户。");
        }

        asyncService.execute(() -> {
            UserRiskCalculateFlowEntity userRiskCalculateFlow = new UserRiskCalculateFlowEntity();
            userRiskCalculateFlow.setUserId(userId)
                    .setType(type)
                    .setDeleteStatus(CommonEnum.ZERO.getValue())
                    .setUpdateTime(date)
                    .setUpdateUser(StringUtils.EMPTY)
                    .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                    .setCreateTime(date)
                    .setCreateUser(userPo.getUsername())
                    .setCreateUserId(userPo.getUserId());
            userRiskCalculateFlowService.saveOrEdit(userRiskCalculateFlow);
        });
    }

    /**
     * 获取字典值
     * @param dictEnum 字典枚举对象
     * @return 字典值
     */
    private int getDictValue(DictEnum dictEnum, int defaultValue) {
        // 本地缓存键
        String cacheKey = EntityConstant.DICT + dictEnum.getValue();

        /*
            本地缓存中获取字典值
         */
        Integer value = GuavaUtils.get(cacheKey, Integer.class);
        if (Objects.nonNull(value)) {
            log.info("本地缓存获取到字典值。cacheKey={} value={}", cacheKey, value);
            return value;
        }

        /*
            获取字典
         */
        DictEntity dict = dictWrapper.getOneDict(dictEnum);
        try {
            // 获取字典值
            value = Integer.parseInt(dict.getValue());

            /*
                本地缓存字典值
             */
            GuavaUtils.set(cacheKey, value);

        } catch (Exception e) {
            log.error("字典值配置错误。{}", dict);
            value = defaultValue;
        }
        return value;
    }

}

